SpringCloud 源码系列(6)

您所在的位置:网站首页 feignclient 参数 SpringCloud 源码系列(6)

SpringCloud 源码系列(6)

#SpringCloud 源码系列(6)| 来源: 网络整理| 查看: 265

SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

SpringCloud 源码系列(2)—— 注册中心 Eureka(中)

SpringCloud 源码系列(3)—— 注册中心 Eureka(下)

SpringCloud 源码系列(4)—— 负载均衡 Ribbon(上)

SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)

SpringCloud 源码系列(6)—— 声明式服务调用 Feign

 

一、Feign 基础入门 1、Feign 概述

在使用 Spring Cloud 开发微服务应用时,各个服务提供者都是以HTTP接口的形式对外提供服务,因此在服务消费者调用服务提供者时,底层通过 HTTP Client 的方式访问。我们可以使用JDK原生的 URLConnection、Apache的HTTP Client、OkHttp、Spring 的 RestTemplate 去实现服务间的调用。但是最方便、最优雅的方式是通过 Spring Cloud OpenFeign 进行服务间的调用。

Feign 是一个声明式的 Web Service 客户端,它的目的就是让Web Service调用更加简单。Spring Cloud 对 Feign 进行了增强,使 Feign 支持 Spring MVC 的注解,并整合了 Ribbon、Hystrix 等。Feign还提供了HTTP请求的模板,通过编写简单的接口和注解,就可以定义好HTTP请求的参数、格式、地址等信息。Feign 会完全代理HTTP的请求,在使用过程中我们只需要依赖注入Bean,然后调用对应的方法传递参数即可。Feign 的首要目标是将 Java HTTP 客户端的书写过程变得简单。

Feign 的一些主要特性如下:

可插拔的注解支持,包括Feign注解和JAX-RS注解。 支持可插拔的HTTP编码器和解码器。 支持 Hystrix 和它的Fallback。支持Ribbon的负载均衡。 支持HTTP请求和响应的压缩。

GitHub地址:

OpenFeign 地址:https://github.com/OpenFeign/feign SpringCloud OpenFeign 地址:https://github.com/spring-cloud/spring-cloud-openfeign 2、DEMO示例

还是使用前面研究 Eureka 和 Ribbon 时的 demo-producer、demo-consumer 服务来做测试。

① 首先,需要引入 openfeign 的依赖

1 2 org.springframework.cloud 3 spring-cloud-starter-openfeign 4

spring-cloud-starter-openfeign 会帮我们引入如下依赖,包含了 OpenFeign 的核心组件。

② 在 demo-consumer 服务中,增加一个 Feign 客户端接口,来调用 demo-producer 的接口。

1 @FeignClient(value = "demo-producer") 2 public interface ProducerFeignClient { 3 4 @GetMapping("/v1/user/{id}") 5 ResponseEntity getUserById(@PathVariable Long id, @RequestParam(required = false) String name); 6 7 @PostMapping("/v1/user") 8 ResponseEntity createUser(@RequestBody User user); 9 10 }

③ 在启动类加上 @EnableFeignClients 注解。

1 @EnableFeignClients 2 @SpringBootApplication 3 public class ConsumerApplication { 4 //.... 5 }

④ 在接口中注入 ProducerFeignClient 就可以使用 Feign 客户端接口来调用远程服务了。

1 @RestController 2 public class FeignController { 3 private final Logger logger = LoggerFactory.getLogger(getClass()); 4 5 @Autowired 6 private ProducerFeignClient producerFeignClient; 7 8 @GetMapping("/v1/user/query") 9 public ResponseEntity queryUser() { 10 ResponseEntity result = producerFeignClient.getUserById(1L, "tom"); 11 User user = result.getBody(); 12 logger.info("query user: {}", user); 13 return ResponseEntity.ok(user); 14 } 15 16 @GetMapping("/v1/user/create") 17 public ResponseEntity createUser() { 18 ResponseEntity result = producerFeignClient.createUser(new User(10L, "Jerry", 20)); 19 User user = result.getBody(); 20 logger.info("create user: {}", user); 21 return ResponseEntity.ok(user); 22 } 23 }

⑤ 在 demo-producer 服务增加 UserController 接口供消费者调用

1 @RestController 2 public class UserController { 3 private final Logger logger = LoggerFactory.getLogger(getClass()); 4 5 @PostMapping("/v1/user/{id}") 6 public ResponseEntity queryUser(@PathVariable Long id, @RequestParam String name) { 7 logger.info("query params: id :{}, name:{}", id, name); 8 return ResponseEntity.ok(new User(id, name, 10)); 9 } 10 11 @PostMapping("/v1/user/{id}") 12 public ResponseEntity createUser(@RequestBody User user) { 13 logger.info("create params: {}", user); 14 return ResponseEntity.ok(user); 15 } 16 }

⑥ 测试

先把把注册中心启起来,然后 demo-producer 启两个实例,再启动 demo-consumer,调用 demo-consumer 的接口测试,会发现,ProducerFeignClient 的调用会轮询到 demo-consumer 的两个实例上。

通过简单的测试可以发现,Feign 使得 Java HTTP 客户端的书写过程变得非常简单,就像开发接口一样。另外,Feign底层一定整合了 Ribbon,@FeignClient 指定了服务名称,请求最终一定是通过 Ribbon 的 ILoadBalancer 组件进行负载均衡的。

3、FeignClient 注解

通过前面的DEMO可以发现,使用 Feign 最核心的应该就是 @EnableFeignClients 和 @FeignClient 这两个注解,@FeignClient 加在客户端接口类上,@EnableFeignClients 加在启动类上,就是用来扫描加了 @FeignClient 接口的类。我们研究源码就从这两个入口开始。

要知道接口是不能直接注入和调用的,那么一定是 @EnableFeignClients 扫描到 @FeignClient 注解的接口后,基于这个接口生成了动态代理对象,并注入到 Spring IOC 容器中,才可以被注入使用。最终呢,一定会通过 Ribbon 负载均衡获取一个 Server,然后重构 URI,再发起最终的HTTP调用。

① @EnableFeignClients 注解

首先看 @EnableFeignClients 的类注释,注释就已经说明了,这个注解就是用来扫描 @FeignClient 注解的接口的,那么核心的逻辑应该就是在 @Import 导入的类 FeignClientsRegistrar 中的。

EnableFeignClients 的主要属性有如下:

value、basePackages: 配置扫描 @FeignClient 的包路径 clients:直接指定扫描的 @FeignClient 接口 defaultConfiguration:配置 Feign 客户端全局默认配置类,从注释可以得知,默认的全局配置类是 FeignClientsConfiguration 1 package org.springframework.cloud.openfeign; 2 3 /** 4 * Scans for interfaces that declare they are feign clients (via 5 * {@link org.springframework.cloud.openfeign.FeignClient} @FeignClient). 6 * Configures component scanning directives for use with 7 * {@link org.springframework.context.annotation.Configuration} 8 * @Configuration classes. 9 */ 10 @Retention(RetentionPolicy.RUNTIME) 11 @Target(ElementType.TYPE) 12 @Documented 13 @Import(FeignClientsRegistrar.class) 14 public @interface EnableFeignClients { 15 16 // 指定扫描 @FeignClient 包所在目录 17 String[] value() default {}; 18 19 // 指定扫描 @FeignClient 包所在目录 20 String[] basePackages() default {}; 21 22 // 指定标记接口来扫描包 23 Class[] basePackageClasses() default {}; 24 25 // Feign 客户端全局默认配置类 26 /** 27 * A custom @Configuration for all feign clients. Can contain override 28 * @Bean definition for the pieces that make up the client, for instance 29 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. 30 * 31 * @see FeignClientsConfiguration for the defaults 32 * @return list of default configurations 33 */ 34 Class[] defaultConfiguration() default {}; 35 36 // 直接指定 @FeignClient 注解的类,这时就会禁用类路径扫描 37 Class[] clients() default {}; 38 }

② @FeignClient 注解

首先看 FeignClient 的类注释,注释说明 @FeignClient 注解就是声明一个 REST 客户端接口,而且会创建一个可以注入的组件,应该就是动态代理的bean。而且如果Ribbon可用,然后就可以用Ribbon做负载均衡,这个负载均衡可以用 @RibbonClient 定制配置类,名称一样就行。

FeignClient 注解被 @Target(ElementType.TYPE) 修饰,表示 FeignClient 注解的作用目标在接口上。@Retention(RetentionPolicy.RUNTIME) 注解表明该注解会在 Class 字节码文件中存在,在运行时可以通过反射获取到。

@FeignClient 注解用于创建声明式 API 接口,该接口是 RESTful 风格的。Feign 被设计成插拔式的,可以注入其他组件和 Feign 一起使用。最典型的是如果 Ribbon 可用,Feign 会和Ribbon 相结合进行负载均衡。

FeignClient 主要有如下属性:

name:指定 FeignClient 的名称,如果项目使用了 Ribbon,name 属性会作为微服务的名称,用于服务发现。 url:url 一般用于调试,可以手动指定 @FeignClient 调用的地址。 decode404:当发生404错误时,如果该字段为true,会调用 decoder 进行解码,否则抛出 FeignException。 configuration:FeignClient 配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contracto fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。 fallbackFactory:工厂类,用于生成 fallback 类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。 path:定义当前 FeignClient 的统一前缀。 1 package org.springframework.cloud.openfeign; 2 3 /** 4 * Annotation for interfaces declaring that a REST client with that interface should be 5 * created (e.g. for autowiring into another component). If ribbon is available it will be 6 * used to load balance the backend requests, and the load balancer can be configured 7 * using a @RibbonClient with the same name (i.e. value) as the feign client. 8 */ 9 @Target(ElementType.TYPE) 10 @Retention(RetentionPolicy.RUNTIME) 11 @Documented 12 @Inherited 13 public @interface FeignClient { 14 15 // 指定服务名称 16 @AliasFor("name") 17 String value() default ""; 18 19 // 指定服务名称,已过期 20 @Deprecated 21 String serviceId() default ""; 22 23 // FeignClient 接口生成的动态代理的bean名称 24 String contextId() default ""; 25 26 // 指定服务名称 27 @AliasFor("value") 28 String name() default ""; 29 30 // @Qualifier 标记 31 String qualifier() default ""; 32 33 // 如果不使用Ribbon负载均衡,就需要使用url返回一个绝对地址 34 String url() default ""; 35 36 // 404 默认抛出 FeignExceptions 异常,设置为true则替换为404异常 37 boolean decode404() default false; 38 39 // Feign客户端配置类,可以定制 Decoder、Encoder、Contract 40 /** 41 * A custom configuration class for the feign client. Can contain override 42 * @Bean definition for the pieces that make up the client, for instance 43 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. 44 * 45 * @see FeignClientsConfiguration for the defaults 46 * @return list of configurations for feign client 47 */ 48 Class[] configuration() default {}; 49 50 // FeignClient 接口的回调类,必须实现客户端接口,并注册为一个bean对象。 51 // 求失败或降级时就会进入回调方法中 52 /** 53 * Fallback class for the specified Feign client interface. The fallback class must 54 * implement the interface annotated by this annotation and be a valid spring bean. 55 * @return fallback class for the specified Feign client interface 56 */ 57 Class fallback() default void.class; 58 59 // 回调类创建工厂 60 Class fallbackFactory() default void.class; 61 62 // URL前缀 63 String path() default ""; 64 65 // 定义为 primary bean 66 boolean primary() default true; 67 } 4、FeignClient 核心组件

从上面已经得知,FeignClient 的默认配置类为 FeignClientsConfiguration,这个类在 spring-cloud-openfeign-core 的 jar 包下,并且每个 FeignClient 都可以定义各自的配置类。

打开这个类,可以发现这个类注入了很多 Feign 相关的配置 Bean,包括 Retryer、FeignLoggerFactory、Decoder、Encoder、Contract 等,这些类在没有 Bean 被注入的情况下,会自动注入默认配置的 Bean。

1 package org.springframework.cloud.openfeign; 2 3 @Configuration(proxyBeanMethods = false) 4 public class FeignClientsConfiguration { 5 @Autowired 6 private ObjectFactory messageConverters; 7 @Autowired(required = false) 8 private List parameterProcessors = new ArrayList(); 9 @Autowired(required = false) 10 private List feignFormatterRegistrars = new ArrayList(); 11 @Autowired(required = false) 12 private Logger logger; 13 14 @Bean 15 @ConditionalOnMissingBean 16 public Decoder feignDecoder() { 17 return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); 18 } 19 20 @Bean 21 @ConditionalOnMissingBean 22 @ConditionalOnMissingClass("org.springframework.data.domain.Pageable") 23 public Encoder feignEncoder(ObjectProvider formWriterProvider) { 24 return springEncoder(formWriterProvider); 25 } 26 27 @Bean 28 @ConditionalOnClass(name = "org.springframework.data.domain.Pageable") 29 @ConditionalOnMissingBean 30 public Encoder feignEncoderPageable( 31 ObjectProvider formWriterProvider) { 32 //... 33 return encoder; 34 } 35 36 @Bean 37 @ConditionalOnMissingBean 38 public Contract feignContract(ConversionService feignConversionService) { 39 return new SpringMvcContract(this.parameterProcessors, feignConversionService); 40 } 41 42 @Bean 43 @ConditionalOnMissingBean 44 public Retryer feignRetryer() { 45 return Retryer.NEVER_RETRY; 46 } 47 48 @Bean 49 @Scope("prototype") 50 @ConditionalOnMissingBean 51 public Feign.Builder feignBuilder(Retryer retryer) { 52 return Feign.builder().retryer(retryer); 53 } 54 55 @Bean 56 @ConditionalOnMissingBean(FeignLoggerFactory.class) 57 public FeignLoggerFactory feignLoggerFactory() { 58 return new DefaultFeignLoggerFactory(this.logger); 59 } 60 61 @Configuration(proxyBeanMethods = false) 62 @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class }) 63 protected static class HystrixFeignConfiguration { 64 @Bean 65 @Scope("prototype") 66 @ConditionalOnMissingBean 67 @ConditionalOnProperty(name = "feign.hystrix.enabled") 68 public Feign.Builder feignHystrixBuilder() { 69 return HystrixFeign.builder(); 70 } 71 72 } 73 74 //... 75 } View Code

这些其实就是 Feign 的核心组件了,对应的默认实现类如下。

如果想自定义这些配置,可增加一个配置类,然后配置到 @FeignClient 的 configuration 上。

① 先定义一个配置类

1 public class ProducerFeignConfiguration { 2 3 @Bean 4 public Retryer feignRetryer() { 5 return new Retryer.Default(); 6 } 7 }

② 配置到 @FeignClient 中

1 @FeignClient(value = "demo-producer", configuration = ProducerFeignConfiguration.class) 2 public interface ProducerFeignClient { 3 4 //... 5 } 5、Feign 属性文件配置

① 全局配置

前面已经了解到,@EnableFeignClients 的 defaultConfiguration 可以配置全局的默认配置bean对象。也可以使用 application.yml 文件来配置。

1 feign: 2 client: 3 config: 4 # 默认全局配置 5 default: 6 connectTimeout: 1000 7 readTimeout: 1000 8 loggerLevel: basic

② 指定客户端配置

@FeignClient 的 configuration 可以配置客户端特定的配置类,也可以使用 application.yml 配置。

1 feign: 2 client: 3 config: 4 # 指定客户端名称 5 demo-producer: 6 # 连接超时时间 7 connectTimeout: 5000 8 # 读取超时时间 9 readTimeout: 5000 10 # Feign日志级别 11 loggerLevel: full 12 # Feign的错误解码器 13 errorDecoder: com.example.simpleErrorDecoder 14 # 配置拦截器 15 requestInterceptors: 16 - com.example.FooRequestInterceptor 17 - com.example.BarRequestInterceptor 18 # 404是否解码 19 decode404: false 20 #Feign的编码器 21 encoder: com.example.simpleEncoder 22 #Feign的解码器 23 decoder: com.example.simpleDecoder 24 #Feign的Contract配置 25 contract: com.example.simpleContract

注意,如果通过Java代码的方式配置过 Feign,然后又通过属性文件的方式配置 Feign,属性文件中Feign的配置会覆盖Java代码的配置。但是可以配置 feign.client.default-to-properties=false 来改变Feign配置生效的优先级。

③ 开启压缩配置

Spring Cloud Feign支持对请求和响应进行GZIP压缩,以提高通信效率。

1 feign: 2 compression: 3 request: 4 # 配置请求GZIP压缩 5 enabled: true 6 # 配置压缩支持的 MIME TYPE 7 mime-types: text/xml,application/xml,application/json 8 # 配置压缩数据大小的下限 9 min-request-size: 2048 10 response: 11 # 配置响应GZIP压缩 12 enabled: true 6、FeignClient 开启日志

Feign 为每一个 FeignClient 都提供了一-个 feign.Logger 实例,可以在配置中开启日志。但是生产环境一般不要开启日志,因为接口调用可能会产生大量日志,一般在开发环境调试开启即可。

① 通过配置文件开启日志

首先设置客户端的 loggerLevel,然后配置 logging.level 日志级别为 debug。

1 feign: 2 client: 3 config: 4 demo-producer: 5 # Feign日志级别 6 loggerLevel: full 7 8 logging: 9 level: 10 # 设置日志输出级别 11 com.lyyzoo.sunny.register.feign: debug

之后调用 FeignClient 就可以看到接口调用日志了:

1 2020-12-30 15:33:02.459 DEBUG 2720 --- [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] ---> GET http://demo-producer/v1/user/1?name=tom HTTP/1.1 2 2020-12-30 15:33:02.459 DEBUG 2720 --- [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById] ---> END HTTP (0-byte body) 3 2020-12-30 15:33:02.462 DEBUG 2720 --- [nio-8020-exec-6] c.l.s.r.feign.ProducerFeignClient : [ProducerFeignClient#getUserById]


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3